package org.apache.shiro.session.mgt;


import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 对用户的线上状态进行规定与扩展{@link org.apache.shiro.session.mgt.SimpleSession} 
 * <p>@author Henry.Y</p>
 * <p>@date 2019-09-10</p>
 */
public class OnlineSession extends SimpleSession{

	/**
	 * <p>Serialization reminder</p>
	 * <p>you _MUST_ change this number if you introduce a change on this class</p>
	 * <p>that is not serialization backwards compatible.Serialization-compatible</p>
	 * <p>changes do not require a change to this number. If you need to generate</p>
	 * <p>a new number in this case,use JDK's 'serialver' program to generate it</p>
	 */
	private static final long serialVersionUID= 7984336956651793809L;

	public static enum OnlineStatus{
		
		on_line("在线"),
		hidden("隐身"),
		force_logout("强制退出");
		
		private String info;
		
		private OnlineStatus(String info){
			this.info= info;
		}
		
		public String getInfo(){
			
			return this.info;
		}
	}
	
	private static final int USER_AGENT_BIT_MASK= 1 << bitIndexCounter++;
	
	private static final int STATUS_BIT_MASK= 1 << bitIndexCounter++;
	
	private String userAgnet;    //用户浏览器类型
	
	private OnlineStatus status= OnlineStatus.on_line;   //在线状态
	
	public OnlineSession(){
		super();
	}
	
	public OnlineSession(String host){
		super(host);
	}
	
	private String systemHost;  //用户登陆Ip

	public String getUserAgnet() {
		return userAgnet;
	}

	public void setUserAgnet(String userAgnet) {
		this.userAgnet = userAgnet;
	}

	public OnlineStatus getStatus() {
		return status;
	}

	public void setStatus(OnlineStatus status) {
		this.status = status;
	}

	public String getSystemHost() {
		return systemHost;
	}

	public void setSystemHost(String systemHost) {
		this.systemHost = systemHost;
	}
	
	private transient boolean attributeChanged= false;    //属性是否改变，优化Session数据同步
	
	public void markAttributeChanged(){
		this.attributeChanged= true;
	}
	
	public void resetAttributeChanged(){
		this.attributeChanged= false;
	}

	public boolean isAttributeChanged() {
		
		return this.attributeChanged;
	}

	public void setAttribute(Object key,Object value){
		super.setAttribute(key, value);
	}
	
	public Object removeAttribute(Object key){
		
		return super.removeAttribute(key);
	}
	
	/**
	 * <p>Serializes this object to the specified output stream for the JDK Serializations.</p>
	 * 
	 * @param out output stream used for Object serialization 
	 * @throws IOException if any of this object's fields could not written to the stream 
	 */
	private void writeObject(ObjectOutputStream out)throws IOException{
		out.defaultWriteObject();
		short alteredFieldsBitMask= getAlteredFieldsBitMask();
		out.writeShort(alteredFieldsBitMask);
		if(null != userAgnet){
			out.writeObject(userAgnet);
		}
		if(null != status){
			out.writeObject(status);
		}
	}

    /**
     * Reconstitutes this object based on the specified InputStream for JDK Serialization.
     * 
     * @param in the input stream to use for reading data to populate this object
     * @throws IOException if the input stream can not be used 
     * @throws ClassNotFoundException if a required class need for instantiation is not available in the present JVM
     */
	private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
		in.defaultReadObject();
		short bitMask= in.readShort();
		
		if(isFieldPresent(bitMask,USER_AGENT_BIT_MASK)){
			this.userAgnet= (String)in.readObject();
		}
		if(isFieldPresent(bitMask,STATUS_BIT_MASK)){
			this.status= (OnlineStatus)in.readObject();
		}
	}
	
	/**
	 * Returns a bit mask used during serialization indicates which fields have been serialized. Fields that have been 
	 * altered (not null and/or not retaining the class default) will be serialized and have 1 in their respective
	 * index, fields that are null and/or retain class default value 0.
	 * 
	 * @return a bit mask during the serialization indicates which fields have been serialized.
	 */
	private short getAlteredFieldsBitMask(){
		int bitMask=0;
		bitMask= userAgnet != null? bitMask | USER_AGENT_BIT_MASK : bitMask;
		bitMask= status != null? bitMask | STATUS_BIT_MASK : bitMask;
		return (short)bitMask;
	}
	
	/** 
	 * Returns {@code true} if the given {@code true} argument indicates that the specified field has been
	 * serialized and therefore should be read during deSerialization,{@code false} otherwise.
	 * 
	 * @param bitMask the aggregate bitMask for all fields have been serialized,Individual bits represent
	 *                the fields that have been serialized,A bit set to 1 means that corresponding field has
	 *                been serialized, 0 means it has not been serialized.  
	 * @param fieldBitMask the field bit mask constant identifying which bit to inspect (corresponds to the class attribute).
	 * @return {@code true} if the given (@code bitMask) argument indicates that the specified field has been
	 *         serialized and therefore should be read during deSerialization,{@code false} otherwise.
	 */                    
	private static boolean isFieldPresent(short bitMask,int fieldBitMask){
		
		return (bitMask & fieldBitMask)!=0;
	}
	
}
